﻿

using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using Org.BouncyCastle.Crypto.Tls;
using System.Net;

namespace FacebookXmpp
{
	internal class Connection : IDisposable
    {
        private readonly Socket _socket;
        private NetworkStream _networkStream;
        private BouncySslStream _sslStream;
        public bool Connected = false;

		private int _bufferStartIndex;
		private readonly byte[] _buffer = new byte[0x8000];
		private StringBuilder _recievedText = new StringBuilder();

        public StringBuilder Debug { get; private set; }

		public bool UseSecure { get; set;  }

		public void StartTls()
		{
			UseSecure = true;

			if (_networkStream == null)
				_networkStream = new NetworkStream( _socket );

            TlsProtocolHandler protocolHandler = new TlsProtocolHandler(_networkStream, _networkStream);

            protocolHandler.Connect(new CertificateValidator());

            _sslStream = new BouncySslStream(protocolHandler.InputStream, protocolHandler.OutputStream);
            
			if (_sslStream != null) 
				return;
    
			lock (Debug)
				Debug.AppendFormat( "** Started TLS **<br/>" );
		}


        public Connection()
        {
            Debug = new StringBuilder();

            var ipAddresses = Dns.GetHostByName("chat.facebook.com").AddressList;
            if (ipAddresses.Length == 0)
                return;

            try
            {
                _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                _socket.Connect(new IPEndPoint(ipAddresses[0].Address, 5222));
                Connected = true;
            }
            catch
            {
                _socket = null;
                Connected = false;
            }

        }
        
        public void Dispose()
        {
            if (_networkStream != null)
                _networkStream.Dispose();
        }

        public void Send(string text)
        {
			if (String.IsNullOrEmpty( text ))
				return;

#if DEBUG
			lock (Debug)
				Debug.AppendFormat( "Out: {0}\n", text );
#endif
            byte[] outp = Encoding.UTF8.GetBytes(text);

            if (UseSecure)
                _sslStream.Write(outp);
            else
                _socket.Send(outp);
        }

        public string Recieve()
        {
        	if (_socket.Available == 0)
                return null;
            
            int bytesRead = 0;
            if (UseSecure)
                bytesRead = _sslStream.Read(_buffer, _bufferStartIndex, _buffer.Length - _bufferStartIndex);
            else
                bytesRead = _socket.Receive(_buffer, _bufferStartIndex, _buffer.Length - _bufferStartIndex, SocketFlags.None);
            
        	ReadBytes( bytesRead );

            var incomming = ClearStringBuffer();
			
#if DEBUG
			lock (Debug)
				Debug.AppendFormat( "In: {0}\n", incomming );
#endif
        	return incomming;
        }
		
		private void ReadBytes( int bytesRead )
		{
			// http://en.wikipedia.org/wiki/UTF-8#Design
			// Top 2 bits are either;
			//   00xx xxxx => 6 bit ASCII mapped character
			//   11xx xxxx => multi byte chatacter Start
			//   10xx xxxx => multi byte chatacter Middle of definition
			// So while last character in buffer is 'middle of definition' rewind end buffer pointer
			int endOfBuffer = bytesRead + _bufferStartIndex;
            if (endOfBuffer == 0)
                return;

            int end = endOfBuffer;
			while ((_buffer[end - 1] & 0xC0) == 0x80) --end;

			string part = Encoding.UTF8.GetString( _buffer, 0, end ).TrimEnd( '\0' );

			if (end != endOfBuffer)
			{
				_bufferStartIndex = endOfBuffer - end;
				for (int i = 0; i < _bufferStartIndex; i++)
					_buffer[i] = _buffer[i + end];
			}

			lock (_recievedText)
				_recievedText.Append( part );
		}


		private string ClearStringBuffer()
		{
			string result;

			lock (_recievedText)
			{
				result = _recievedText.ToString();
				_recievedText = new StringBuilder();
			}

			return result;
		}
    }

    internal class CertificateValidator : ICertificateVerifyer
    {
        public bool IsValid(Org.BouncyCastle.Asn1.X509.X509CertificateStructure[] certs)
        {
            return true;
        }
    }

    internal class BouncySslStream
    {
        private Stream _input;
        private Stream _output;

        internal BouncySslStream(Stream inputStream, Stream outputStream)
        {
            _input = inputStream;
            _output = outputStream;
        }

        internal void Write(byte[] output)
        {
            _output.Write(output, 0, output.Length);
        }

        internal int Read(byte[] _buffer, int _bufferStartIndex, int p)
        {
            return _input.Read(_buffer, _bufferStartIndex, p);
        }
    }
}
